package han.cirno.corrupt.components.school.impl

import han.cirno.corrupt.R
import han.cirno.corrupt.data.UserUnstructuredData
import han.cirno.corrupt.data.instances.UserSchoolData
import han.cirno.corrupt.components.school.SchoolProvider
import han.cirno.corrupt.throwables.LoginExceptions
import han.cirno.corrupt.util.ViewUtil
import han.cirno.corrupt.util.getBodyStringAndClose
import okhttp3.FormBody
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

abstract class CommonNetClient<T: CommonNetClient.Payload>(
    private val loginRequestProvider:()->Request,
    private val loopbackCheck:(HttpUrl)->Boolean,
    val implCookieJarSource:()-> SerializableCookies ={UserSchoolData.instance.cookies},
    val okHttpClient: OkHttpClient=OkHttpClient.Builder().cookieJar(implCookieJarSource()).build()) {

    open fun request(payload: T?, loginCallback: SchoolProvider.LoginCallback?=null):Result<String>{
        var attempt=5
        val nonFatalExceptions= arrayOf(
            LoginExceptions.NetworkExceptionWithResId::class,
            SocketTimeoutException::class,
            SocketException::class,
            UnknownHostException::class,
            IOException::class)
        var lastException:Exception= Exception("")
        val implCookieJar=implCookieJarSource()
        do {
            try {
                loginCallback?.onProgress(ViewUtil.getString(R.string.login_message_verify))
                val tryResponse = okHttpClient.newCall(loginRequestProvider()).execute()
                if(tryResponse.use {
                    if (loopbackCheck(tryResponse.request.url)) {
                        loginCallback?.onProgress(ViewUtil.getString(R.string.login_message_first_login))
                        attempt--
                        tryLogin(tryResponse)
                        return@use true
                    }
                    return@use false
                })continue
                UserUnstructuredData.saveData()
                if (payload==null)return Result.success("Success without payload")
                val request = payload.builder().build()
                val responseString = okHttpClient.newCall(request).execute().getBodyStringAndClose()?:throw IOException("Response is null")
                if (responseString.isBlank())throw IOException("Response is blank")
                return Result.success(responseString)
            }catch(e:Exception){
                e.printStackTrace()
                val isFatal = !nonFatalExceptions.contains(e::class)
                lastException=e
                implCookieJar.cookies.clear()
                e.printStackTrace()
                attempt--
                loginCallback?.onPartialFailed(e.localizedMessage?:"",isFatal,e)
                if (isFatal) return Result.failure(e)
                continue
            }
        }while (attempt>0)
        return Result.failure(lastException)
    }

    abstract fun tryLogin(tryLoginResponse: Response)

    abstract class Payload(
        private val scheme:String,
        private val host:String,
        private val path:String,
        private val queryPairs:Array<String>,
        private val postForm:Array<String>?=null,
        private val headers:Array<String>?=null){

        open fun builder(): Request.Builder{
            val urlBuilder = HttpUrl.Builder()
                .scheme(scheme)
                .host(host)
                .encodedPath(path)
            if (queryPairs.size and 1==0)
                for (i in queryPairs.indices step 2)
                    urlBuilder.addQueryParameter(queryPairs[i],queryPairs[i+1])
            val requestBuilder = Request.Builder().url(urlBuilder.build())
            if (postForm!=null&&postForm.size and 1==0){
                val formBuilder = FormBody.Builder()
                for (i in postForm.indices step 2)
                    formBuilder.add(postForm[i],postForm[i+1])
                requestBuilder.post(formBuilder.build())
            }
            if (headers!=null&&headers.size and 1==0){
                for (i in headers.indices step 2)
                    requestBuilder.addHeader(headers[i],headers[i+1])
            }
            return requestBuilder
        }
    }
}
